package main

import (
	"context"
	"crypto/md5"
	"encoding/hex"
	"fmt"
	"io"
	"io/fs"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"runtime"
	"syscall"

	wails "github.com/wailsapp/wails/v2/pkg/runtime"
)

// 事件
const (
	eventUpdateProgress = "update_progress"
	eventUpdateData     = "update_data"

	noticeMessage = "notice_message"

	extPattern = "*.png"

	statusWait    = 0
	statusWorking = 1
	statusDone    = 2
	statusFail    = 3
)

var JobLimit = 3

var DoneNum = 0

type Message struct {
	Message string `json:"message"`
	Type    string `json:"type"`
}

type FileItem struct {
	Id     string `json:"id"`
	Path   string `json:"path"`
	Name   string `json:"name"`
	Size   int64  `json:"before"`
	After  int64  `json:"after"`
	Status int    `json:"status"`
}

var FileList []*FileItem

var outputPath = ""

var coverOrigin = true

var binPath = ""

// App struct
type App struct {
	ctx context.Context
}

// NewApp creates a new App application struct
func NewApp() *App {
	return &App{}
}

// startup is called when the app starts. The context is saved
// so we can call the runtime methods
func (a *App) startup(ctx context.Context) {
	a.ctx = ctx

	JobLimit = runtime.NumCPU()
	fmt.Println("jobLimit", JobLimit)

	a.extractBinary()
}

func (a *App) checkOrCreateDir(path string) error {
	_, err := os.Stat(path)
	if err == nil {
		return nil
	}
	if os.IsNotExist(err) {
		res := os.Mkdir(path, 0555)
		if res != nil {
			return res
		}
		return nil
	}
	return nil
}

func (a *App) extractBinary() {
	name := "pngquant.exe"
	tmp := "lib"
	err := a.checkOrCreateDir(tmp)
	if err != nil {
		wails.LogFatal(a.ctx, err.Error())
	}

	var in fs.File
	if runtime.GOOS == "windows" {
		in, err = binaryWin.Open(path.Join("pngquant", name))
		if err != nil {
			wails.LogFatal(a.ctx, err.Error())
		}
	}
	out, err := os.Create(path.Join(tmp, name))
	if err != nil {
		wails.LogFatal(a.ctx, err.Error())
	}
	io.Copy(out, in)
	out.Close()
	in.Close()

	binPath, err = filepath.Abs(filepath.Join(tmp, name))
	if err != nil {
		wails.LogFatal(a.ctx, err.Error())
	}
}

func (a *App) ChangeOutputPath() string {
	res, err := wails.OpenDirectoryDialog(a.ctx, wails.OpenDialogOptions{})
	if err != nil {
		wails.EventsEmit(a.ctx, noticeMessage, &Message{
			Message: err.Error(),
			Type:    "error",
		})
		return ""
	}
	outputPath = filepath.Clean(res)

	return outputPath
}

func (a *App) InputOutputPath(path string) string {
	outputPath = filepath.Clean(path)
	return outputPath
}

func (a *App) ChangeCoverOrigin(val bool) {
	coverOrigin = val
}

// 压缩文件
func (a *App) Process(item *FileItem, ch chan int) {
	defer func() {
		<-ch
	}()
	item.Status = statusWorking

	var output string
	if coverOrigin {
		output = item.Path
	} else {
		output = filepath.Clean(filepath.Join(outputPath, item.Name))
	}

	fmt.Println(output)

	args := []string{
		"--verbose",
		"--strip",
		"--force",
		"--speed", "1",
		"--output", output,
		item.Path,
	}

	if coverOrigin || outputPath == filepath.Clean(filepath.Dir(item.Path)) {
		args = append([]string{"--skip-if-larger"}, args...)
	}

	cmd := exec.Command(binPath, args...)
	// windows
	cmd.SysProcAttr = &syscall.SysProcAttr{
		HideWindow:    true,
		CreationFlags: 0x08000000,
	}
	err := cmd.Run()
	if err != nil {
		fmt.Println(err.Error())
		item.Status = statusFail
	} else {
		item.Status = statusDone

		info, err := os.Stat(output)
		if err != nil {
			return
		}
		after := info.Size()
		item.After = after
	}

	wails.EventsEmit(a.ctx, eventUpdateProgress, nil)
	wails.EventsEmit(a.ctx, eventUpdateData, item)
}

func (a *App) Start() bool {
	if len(FileList) == 0 {
		wails.EventsEmit(a.ctx, noticeMessage, &Message{
			Message: "请选择文件",
			Type:    "error",
		})
		return false
	}
	if !coverOrigin && outputPath == "" {
		wails.EventsEmit(a.ctx, noticeMessage, &Message{
			Message: "请选择输出目录",
			Type:    "error",
		})
		return false
	}

	ch := make(chan int, JobLimit)

	for _, v := range FileList {
		ch <- 1
		go a.Process(v, ch)
	}

	return true
}

func (a *App) ReadFile(path string) *FileItem {
	fileInfo, err := os.Stat(path)
	if err != nil {
		return nil
	}

	name := filepath.Base(path)
	size := fileInfo.Size()

	h := md5.New()
	h.Write([]byte(path))
	id := hex.EncodeToString(h.Sum(nil))

	return &FileItem{
		Id:   id,
		Path: path,
		Name: name,
		Size: size,
	}
}

func (a *App) ListDir() []*FileItem {
	list, err := wails.OpenMultipleFilesDialog(a.ctx, wails.OpenDialogOptions{
		Filters: []wails.FileFilter{
			{
				Pattern: extPattern,
			},
		},
	})
	if err != nil {
		wails.EventsEmit(a.ctx, noticeMessage, &Message{
			Message: err.Error(),
			Type:    "error",
		})
		return nil
	}

	FileList = make([]*FileItem, len(list))
	for i, v := range list {
		FileList[i] = a.ReadFile(v)
	}

	return FileList
}
